package com.atguigu.tingshu.account.service.impl;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Maps;

import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.model.account.UserAccount;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;
	@Autowired
	private UserAccountDetailMapper userAccountDetailMapper ;

	@Autowired
	private RedisTemplate<String , String> redisTemplate ;

	@Override
	public void addUserAccount(String value) {

		UserAccount userAccount = new UserAccount();
		userAccount.setUserId(Long.parseLong(value));
		userAccount.setTotalAmount(new BigDecimal("0"));
		userAccount.setLockAmount(new BigDecimal("0"));
		userAccount.setAvailableAmount(new BigDecimal("0"));
		userAccount.setTotalIncomeAmount(new BigDecimal("0"));
		userAccount.setTotalPayAmount(new BigDecimal("0"));
		userAccountMapper.insert(userAccount);

	}

	@Override
	public BigDecimal getAvailableAmount() {
		// 获取当前登录的用户
		Long userId = AuthContextHolder.getUserId();

		// 根据用户id查询用户的账号信息
		LambdaQueryWrapper<UserAccount> lambdaQueryWrapper = new LambdaQueryWrapper<>() ;
		lambdaQueryWrapper.eq(UserAccount::getUserId, userId);
		UserAccount userAccount = userAccountMapper.selectOne(lambdaQueryWrapper);

		// 返回账号的可用金额
		return userAccount.getAvailableAmount();

	}

	@Override
	public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo) {   //两个优化  1.使用redis保证接口的幂等性是为了解决重复请求
																			//			2.使用高级数据库的悲观锁 防止线程插入
		// 获取交易号  因为openfeign存在重试机制
		String orderNo = accountLockVo.getOrderNo() ;
		// 基于Redis保证接口的幂等性  ----> setnx命令
		String idempotentKey = "check:idempotent:" + orderNo ;
		String lockResultRedisKey = "account:lock:result:" + orderNo ;
		//成功设置了 证明为第一次消费  设置失败证明为重复消费
		Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 5, TimeUnit.MINUTES);
		if(!aBoolean) {		// 重复请求
			//重复请求 也可能没有执行完 存redis数据 所以我们要进行判断 redis里面有没有数据
			String lockResultJSON = redisTemplate.opsForValue().get(lockResultRedisKey);
			//不一定能获取到redis数据 在获取完accountLockVo对象保存redis之前可能就会触发openfeign的重试机制 导致redis没有数据
			if(!StringUtils.isEmpty(lockResultJSON)) {
				AccountLockResultVo accountLockResultVo = JSON.parseObject(lockResultJSON, AccountLockResultVo.class);
				return accountLockResultVo;
			}else {
				// 还未计算出结果就再次请求
				throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_REPEAT) ;		// 封装的业务状态码为223
			}

		}

		// 获取accountLockVo对象的相关参数
		Long userId = accountLockVo.getUserId() ;
		String content = accountLockVo.getContent() ;
		BigDecimal amount = accountLockVo.getAmount() ;

		// 检查可用金额 在检查可用金额线程运行时 不被打扰 我们在数据库使用悲观锁 就是在后面加一个for update
		UserAccount userAccount = userAccountMapper.check(userId , amount) ;
		if(userAccount == null) {		// 可用余额不足
			redisTemplate.delete(idempotentKey) ;			// 删除幂等性key的保证
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS) ;		// 封装的业务状态码219
		}

		// 锁定可用金额
		int count = userAccountMapper.lock(userId , amount) ;  // int类型的返回值表示的就是影响数据行锁
		if(count == 0) {
			redisTemplate.delete(idempotentKey) ;			// 删除幂等性key的保证
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR) ;   // 封装的业务状态码220
		}

		// 记录账户变更的日志数据(user_account_detail)
		this.log(userId , "锁定:" + content ,
				SystemConstant.ACCOUNT_TRADE_TYPE_LOCK , amount , orderNo);

		// 构建响应结果数据
		AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
		accountLockResultVo.setUserId(userId);
		accountLockResultVo.setAmount(amount);
		accountLockResultVo.setContent(content);
		//redis的值在检查并锁定可用锁的时候 已经有值了
		redisTemplate.opsForValue().set(lockResultRedisKey , JSON.toJSONString(accountLockResultVo) , 1 , TimeUnit.HOURS);

		// 返回
		return accountLockResultVo;
	}

	@Override
	public void minusAccount(String orderNo) {

		//保证接口的幂等性
		String idempotentKey = "check:idempotent:" + orderNo ;
		Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo);
		if(!aBoolean){ //查询不到数据 说明数据 已经存入 并且删除
			return;
		}

		//扣减用户的金额
		//!!!!在检查和锁定我们的金额的时候,我们已经在redis里面存了我们的账户锁定结果数据
		String lockResultRedisKey = "account:lock:result:" + orderNo ;
		String json = redisTemplate.opsForValue().get(lockResultRedisKey);
		if(StringUtils.isEmpty(json)){
			return;
		}
		//不为空进行金额的扣减
		AccountLockResultVo accountLockResultVo = JSON.parseObject(json, AccountLockResultVo.class);

		int count = userAccountMapper.minusAccount(accountLockResultVo.getUserId(),accountLockResultVo.getAmount());
		if(count==0){  //说明影响的行数是0
			redisTemplate.delete(idempotentKey) ;
			throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
		}


		// 记录账户变更的日志数据
		this.log(accountLockResultVo.getUserId() , "解锁: " + accountLockResultVo.getContent() ,
				SystemConstant.ACCOUNT_TRADE_TYPE_MINUS , accountLockResultVo.getAmount() , orderNo);

		// 从Redis中删除锁定账户金额的结果数据
		redisTemplate.delete(lockResultRedisKey) ;



	}

	@Override
	public void unLockAccount(String orderNo) {
		//就是用户取消支付 导入保存数据异常 进行锁金额的释放

		//根据key取存到redis里面的数据
		String lockResultRedisKey = "account:lock:result:" + orderNo ;
		String unlockredis = redisTemplate.opsForValue().get(lockResultRedisKey);
		if(StringUtils.isEmpty(unlockredis)){
			return;
		}

		//有数值的话对数据进行解析
		AccountLockResultVo accountLockResultVo = JSON.parseObject(unlockredis, AccountLockResultVo.class);
		int count = userAccountMapper.unLock(accountLockResultVo.getUserId(),accountLockResultVo.getAmount());
	}

	private void log(Long userId , String title, String tradeType, BigDecimal amount , String orderNo) {
		UserAccountDetail userAccountDetail = new UserAccountDetail() ;
		userAccountDetail.setUserId(userId);
		userAccountDetail.setTitle(title);
		userAccountDetail.setTradeType(tradeType);
		userAccountDetail.setAmount(amount);
		userAccountDetail.setOrderNo(orderNo);
		userAccountDetailMapper.insert(userAccountDetail) ;
	}
}
